﻿package extremefx.drawing {
	import extremefx.drawing.colormodels.LuvColor;	
	import extremefx.drawing.colormodels.CmyColor;
	import extremefx.drawing.colormodels.CmykColor;
	import extremefx.drawing.colormodels.LabColor;
	import extremefx.drawing.colormodels.LchColor;
	import extremefx.drawing.colormodels.XyzColor;
	import extremefx.drawing.colormodels.YxyColor;		

	/**
	 * @author Marcelo Volmaro
	 */
	public final class ColorConversions {
		private static const _CIE:Object = {
			"cie1931":{
				"A":[109.850, 35.585], "C":[98.074, 118.232], "D50":[96.422, 82.521], "D55":[95.682, 92.149], "D65":[95.047, 108.883], "D75":[94.972, 122.638], "F2":[99.187, 67.395], "F7":[95.044, 108.755], "F11":[100.966, 64.370]
			}, "cie1964":{
				"A":[111.144, 35.200], "C":[97.285, 116.145], "D50":[96.720, 81.427], "D55":[95.799, 90.926], "D65":[94.811, 107.304], "D75":[94.416, 120.641], "F2":[103.280, 69.026], "F7":[95.792, 107.687], "F11":[103.866, 65.627]
			}
		};

		public static function rgbTocmy(pColor:Color):CmyColor {
			return new CmyColor(1 - (pColor.red / 255), 1 - (pColor.green / 255), 1 - (pColor.blue / 255));
		}

		public static function rgbTocmyk(pColor:Color):CmykColor {
			var round:Function = Math.round;
			var min:Function = Math.min;
			
			var c:Number = 1 - (pColor.red / 255);
			var m:Number = 1 - (pColor.green / 255);
			var y:Number = 1 - (pColor.blue / 255);
			var k:Number = min(y, min(m, min(c, 1)));
			var div:Number = 100 / (1 - k); 
			
			c = round(c - k) * div || 0;
			m = round(m - k) * div || 0;
			y = round(y - k) * div || 0;

			return new CmykColor(c, m, y, round(k * 100));
		}

		public static function rgbToXyz(pColor:Color):XyzColor {
			var r:Number = _adjustXyz(pColor.red);
			var g:Number = _adjustXyz(pColor.green);
			var b:Number = _adjustXyz(pColor.blue);
			
			//Observer. = 2°, Illuminant = D65
			return new XyzColor(
				_zero(r * 0.4124 + g * 0.3576 + b * 0.1805), 
				_zero(r * 0.2126 + g * 0.7152 + b * 0.0722), 
				_zero(r * 0.0193 + g * 0.1192 + b * 0.9505)
			);
		}

		public static function rgbToLab(pColor:Color, pCieIlluminant:String = "D65", pCieObserver:String = "cie1931"):LabColor {
			return xyzToLab(rgbToXyz(pColor), pCieIlluminant, pCieObserver);
		}

		public static function xyzToLab(pXyzColor:XyzColor, pCieIlluminant:String = "D65", pCieObserver:String = "cie1931"):LabColor {
			var illuminant:Array = _CIE[pCieObserver][pCieIlluminant];
			var x:Number = _adjustLab(pXyzColor.x / illuminant[0]);
			var y:Number = _adjustLab(pXyzColor.y / 100);
			var z:Number = _adjustLab(pXyzColor.z / illuminant[1]);
		
			return new LabColor((116 * y) - 16, 500 * (x - y), 200 * (y - z));
		}

		public static function labToLch(pLabColor:LabColor):LchColor {
			var h:Number = Math.atan2(pLabColor.b, pLabColor.a);
			if (h > 0) {
				h = (h / Math.PI) * 180;
			} else {
				h = 360 - (Math.abs(h) / Math.PI) * 180;
			}
			
			return new LchColor(pLabColor.l, Math.sqrt(Math.pow(pLabColor.a, 2) + Math.pow(pLabColor.b, 2)), h);
		}
		
		public static function lchToLab(pLchColor:LchColor):LabColor {
			var h:Number = (Math.PI / 180) * pLchColor.h;
			return new LabColor(
				pLchColor.l,
				Math.cos(h) * pLchColor.c,
				Math.sin(h) * pLchColor.c
			);
		}
		
		public static function yxyToXyz(pYxyColor:YxyColor):XyzColor {
			return new XyzColor(pYxyColor.x * (pYxyColor.Y / pYxyColor.y), pYxyColor.Y, (1 - pYxyColor.x - pYxyColor.y) * (pYxyColor.Y / pYxyColor.y));
		}

		public static function xyzToLuv(pXyzColor:XyzColor, pCieIlluminant:String = "D65", pCieObserver:String = "cie1931"):LuvColor {
			var illuminant:Array = _CIE[pCieObserver][pCieIlluminant];
			
			var u:Number = (4 * pXyzColor.x) / (pXyzColor.x + (15 * pXyzColor.y) + (3 * pXyzColor.z));
			var v:Number = (9 * pXyzColor.y) / (pXyzColor.x + (15 * pXyzColor.y) + (3 * pXyzColor.z));
			var y:Number = pXyzColor.y / 100;

			if(y > 0.008856) { 
				y = Math.pow(y, 1 / 3); 
			} else { 
				y = (7.787 * y) + (16 / 116); 
			}

			var _x:Number = illuminant[0];
			var _y:Number = 100;
			var _z:Number = illuminant[1];
			var _l:Number = (116 * y) - 16;
			var _u:Number = (4 * _x) / (_x + (15 * _y) + (3 * _z));
			var _v:Number = (9 * _y) / (_x + (15 * _y) + (3 * _z));

			return new LuvColor(_l, _zero(13 * _l * (u - _u)), _zero(13 * _l * (v - _v)));
		}

		public static function xyzToRgb(pXyzColor:XyzColor):Color {
			var x:Number = pXyzColor.x / 100;
			var y:Number = pXyzColor.y / 100;
			var z:Number = pXyzColor.z / 100;
			 
			return new Color(
				_adjustRgb(x * 3.2406 + y * -1.5372 + z * -0.4986), 
				_adjustRgb(x * -0.9689 + y * 1.8758 + z * 0.0415), 
				_adjustRgb(x * 0.0557 + y * -0.2040 + z * 1.0570)
			);
		}
		
		public static function xyzToYxy(pXyzColor:XyzColor):YxyColor {
			return new YxyColor(
				pXyzColor.y,
				_zero(pXyzColor.x/(pXyzColor.x+pXyzColor.y+pXyzColor.z)), 
				_zero(pXyzColor.y/(pXyzColor.x+pXyzColor.y+pXyzColor.z))
			);
		}
		
		private static function _zero(pIn:Number):Number {
			if (isNaN(pIn)) return 0;
			return pIn;	
		}

		private static function _adjustRgb(pIn:Number):Number {
			if(pIn > 0.0031308) { 
				pIn = 1.055 * (Math.pow(pIn, 1 / 2.4)) - 0.055; 
			} else { 
				pIn = 12.92 * pIn; 
			}

			pIn *= 255; 
			return(Math.max(0, Math.round(pIn)));
		}

		private static function _adjustXyz(pIn:Number):Number {
			pIn /= 255;
			 
			if(pIn > 0.04045) { 
				pIn = Math.pow(((pIn + 0.055) / 1.055), 2.4);
			} else { 
				pIn = pIn / 12.92; 
			}
 
			return pIn * 100;
		}

		private static function _adjustLab(pIn:Number):Number {
			if (pIn > 0.008856) { 
				return(Math.pow(pIn, 1 / 3));
			} else { 
				return((7.787 * pIn) + (16 / 116));
			}
		}
	}
}
